//------------------------------------------------------------------------------
// PascalESE - a Delphi/Pascal class library for Microsoft ESENT database API
// Index Seek Test Sample - TestCase implementation
//
// Copyright (c) 2010-2011 Jens Geyer
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//------------------------------------------------------------------------------
unit ImplCrossIndexTest;

interface

uses Classes, Windows, SysUtils, Math,
     esent, PascalESE, TestCaseData;

type
  TCrossproductTestCase = class( TTestCaseBase)
  protected
    procedure SetBase(     const pUpd : IJetTableUpdate; const aCount : Integer);
    procedure SetSauce(    const pUpd : IJetTableUpdate; const aCount : Integer);
    procedure SetVegtbls(  const pUpd : IJetTableUpdate; const aCount : Integer);
    procedure SetMeatFish( const pUpd : IJetTableUpdate; const aCount : Integer);
    procedure SetCheese(   const pUpd : IJetTableUpdate; const aCount : Integer);
    procedure FillTable( const pTab : IJetTable);

    function  ColumnContainsData( const pTab : IJetTable; aColumn, aData : string; out nItagNr : Cardinal) : Boolean;
    procedure PerformTest( const pTab : IJetTable);

    procedure RunAllTests;  override;
  end;


implementation

const
  BASES : array[0..4] of string
        = ( 'Classic Napoli',
            'Classic Roma',
            'Extra Thin',
            'Whole Wheat',
            'New York Style');

  SAUCES : array[0..3] of string
         = ( 'Tomato',
             'Garlic',
             'Pesto',
             'No sauce');

  VEGETABLES : array[0..12] of string
           = ( 'Artichoke',
               'Avocado',
               'Beans',
               'Broccoli',
               'Carrot',
               'Chili',
               'Egg',
               'Mushrooms',
               'Onions',
               'Olives',
               'Spinach',
               'Tomatoes',
               'Zucchini');

  MEAT_FISH : array[0..9] of string
           = ( 'Bacon',
               'Chicken',
               'Duck',
               'Lobster',
               'Prawns',
               'Salami',
               'Salmon',
               'Shrimps',
               'Tuna',
               'Turkey'
              );

  CHEESES : array[0..6] of string
          = ( 'Camembert',
              'Cheddar',
              'Gorgonzola',
              'Parmesan',
              'Mozzarella',
              'Gouda',
              'Gruyere');


  // max. ingredients per category
  MAX_BASE       = 1;
  MAX_SAUCE      = 1;
  MAX_VEGETABLE  = 4;
  MAX_MEAT_FISH  = 2;
  MAX_CHEESE     = 2;

  NUMBER_OF_RECORDS = 4096;
  NUMBER_OF_TESTS   = 100;


//--- TCrossproductTestCase ------------------------------------------------------------


procedure TCrossproductTestCase.SetBase( const pUpd : IJetTableUpdate; const aCount : Integer);
var used  : array[0..Length(BASES)-1] of Boolean;
    i,iNext : Integer;
begin
  FillChar( used, SizeOf(used), 0);
  for iNext := 1 to aCount do begin
    repeat
      i := Random( Length(BASES));
    until not used[i];

    pUpd.SetColumnData( COL_BASE, BASES[i], iNext);
    used[i] := TRUE;
  end;
end;


procedure TCrossproductTestCase.SetSauce( const pUpd : IJetTableUpdate; const aCount : Integer);
var used  : array[0..Length(SAUCES)-1] of Boolean;
    i,iNext : Integer;
begin
  FillChar( used, SizeOf(used), 0);
  for iNext := 1 to aCount do begin
    repeat
      i := Random( Length(SAUCES));
    until not used[i];

    pUpd.SetColumnData( COL_SAUCE, SAUCES[i], iNext);
    used[i] := TRUE;
  end;
end;


procedure TCrossproductTestCase.SetVegtbls( const pUpd : IJetTableUpdate; const aCount : Integer);
var used  : array[0..Length(VEGETABLES)-1] of Boolean;
    i,iNext : Integer;
begin
  FillChar( used, SizeOf(used), 0);
  for iNext := 1 to aCount do begin
    repeat
      i := Random( Length(VEGETABLES));
    until not used[i];

    pUpd.SetColumnData( COL_VEGETBLS, VEGETABLES[i], iNext);
    used[i] := TRUE;
  end;
end;


procedure TCrossproductTestCase.SetMeatFish( const pUpd : IJetTableUpdate; const aCount : Integer);
var used  : array[0..Length(MEAT_FISH)-1] of Boolean;
    i,iNext : Integer;
begin
  FillChar( used, SizeOf(used), 0);
  for iNext := 1 to aCount do begin
    repeat
      i := Random( Length(MEAT_FISH));
    until not used[i];

    pUpd.SetColumnData( COL_MEATFISH, MEAT_FISH[i], iNext);
    used[i] := TRUE;
  end;
end;


procedure TCrossproductTestCase.SetCheese( const pUpd : IJetTableUpdate; const aCount : Integer);
var used  : array[0..Length(CHEESES)-1] of Boolean;
    i,iNext : Integer;
begin
  FillChar( used, SizeOf(used), 0);
  for iNext := 1 to aCount do begin
    repeat
      i := Random( Length(CHEESES));
    until not used[i];

    pUpd.SetColumnData( COL_CHEESE, CHEESES[i], iNext);
    used[i] := TRUE;
  end;
end;


procedure TCrossproductTestCase.FillTable( const pTab : IJetTable);
// fill the table with Country/City test data
var i      : Integer;
    nItags : Cardinal;
    pUpd   : IJetTableUpdate;
const
  MSG_FEWDATA = 'Too few data at %s: expected %d, found %d';
begin
  Writeln( Oem( 'Creating test data ...'));

  // check the limits
  ASSERT( Length(BASES)      >= MAX_BASE);
  ASSERT( Length(SAUCES)     >= MAX_SAUCE);
  ASSERT( Length(VEGETABLES) >= MAX_VEGETABLE);
  ASSERT( Length(MEAT_FISH)  >= MAX_MEAT_FISH);
  ASSERT( Length(CHEESES)    >= MAX_CHEESE);


  // empty the table
  pTab.MoveFirst;
  while not pTab.Eof do begin
    pTab.DeleteCurrentRow;
    pTab.MoveFirst;
  end;

  // fill in the data
  for i := 1 to NUMBER_OF_RECORDS do begin

    pUpd := pTab.PrepareUpdate( JET_prepInsert);
    //
    SetBase(     pUpd, MAX_BASE);
    SetSauce(    pUpd, MAX_SAUCE);
    SetVegtbls(  pUpd, MAX_VEGETABLE);
    SetMeatFish( pUpd, MAX_MEAT_FISH);
    SetCheese(   pUpd, MAX_CHEESE);
    //
    pUpd.DoUpdate;
  end;

  // check the data
  Writeln( Oem( 'Verifying test data ...'));
  pTab.SetIndex( IDX_ID);
  pTab.MoveFirst;
  while not pTab.Eof do begin

    pTab.GetColumnMaxItag( COL_BASE, nItags);
    Expecting( nItags = MAX_BASE, Format( MSG_FEWDATA, [COL_BASE, MAX_BASE, nItags]));

    pTab.GetColumnMaxItag( COL_SAUCE, nItags);
    Expecting( nItags = MAX_SAUCE, Format( MSG_FEWDATA, [COL_SAUCE, MAX_SAUCE, nItags]));

    pTab.GetColumnMaxItag( COL_VEGETBLS, nItags);
    Expecting( nItags = MAX_VEGETABLE, Format( MSG_FEWDATA, [COL_VEGETBLS, MAX_VEGETABLE, nItags]));

    pTab.GetColumnMaxItag( COL_MEATFISH, nItags);
    Expecting( nItags = MAX_MEAT_FISH, Format( MSG_FEWDATA, [COL_MEATFISH, MAX_MEAT_FISH, nItags]));

    pTab.GetColumnMaxItag( COL_CHEESE, nItags);
    Expecting( nItags = MAX_CHEESE, Format( MSG_FEWDATA, [COL_CHEESE, MAX_CHEESE, nItags]));

    pTab.MoveNext;
  end;
end;


function TCrossproductTestCase.ColumnContainsData( const pTab : IJetTable; aColumn, aData : string; out nItagNr : Cardinal) : Boolean;
// returns TRUE if the column contains the data string
var sTest : WideString;
begin
  result := FALSE;
  pTab.GetColumnMaxItag( aColumn, nItagNr);
  while (nItagNr > 0) do begin
    pTab.GetColumnData( aColumn, sTest, nItagNr);
    result := SameText( sTest, aData);
    if result then Exit;

    Dec( nItagNr);  // decrement not too early, since this is an "out" var
  end;
end;


procedure TCrossproductTestCase.PerformTest( const pTab : IJetTable);
// searches various combinations and verifies the result
var i, iID, iFound, iRest : Integer;
    entries : array[1..5] of Cardinal;
    nDummy : Cardinal;
    sBase,sSauce,sVegtbl,sMeatFish,sCheese : string;
begin
  Writeln( Oem( 'Testing ...'));

  // fill in the data
  for i := 1 to NUMBER_OF_TESTS do begin

    // what do we want?
    sBase     := BASES[      Random( Length(BASES))];
    sSauce    := SAUCES[     Random( Length(SAUCES))];
    sVegtbl   := VEGETABLES[ Random( Length(VEGETABLES))];
    sMeatFish := MEAT_FISH[  Random( Length(MEAT_FISH))];
    sCheese   := CHEESES[    Random( Length(CHEESES))];
    Writeln( Oem( Format( 'Searching for %s, %s, %s, %s, %s ...',
                          [ sBase,sSauce,sVegtbl,sMeatFish,sCheese])));

    // do we find anything (using the index) that meets our expectations?
    iFound := 0;
    if pTab.Seek( IDX_FULL_PIZZA, [sBase,sSauce,sVegtbl,sMeatFish,sCheese])
    then while not pTab.Eof do begin
      FillChar( entries, SizeOf(entries), 0);
      pTab.GetColumnData( COL_ID, iID);
      Expecting( ColumnContainsData( pTab, COL_BASE,     sBase,     entries[1]), sBase+' not found');
      Expecting( ColumnContainsData( pTab, COL_SAUCE,    sSauce,    entries[2]), sSauce+' not found');
      Expecting( ColumnContainsData( pTab, COL_VEGETBLS, sVegtbl,   entries[3]), sVegtbl+' not found');
      Expecting( ColumnContainsData( pTab, COL_MEATFISH, sMeatFish, entries[4]), sMeatFish+' not found');
      Expecting( ColumnContainsData( pTab, COL_CHEESE,   sCheese,   entries[5]), sCheese+' not found');
      pTab.MoveNext;
      Inc( iFound);
      Writeln( Oem( Format( '-> Testpizza %d has these ingredients at %d, %d, %d, %d, %d',
                            [ iID, entries[1], entries[2], entries[3], entries[4], entries[5]])));
    end;

    // we should now have a given number of non-matching records, shouldn't we?
    iRest := NUMBER_OF_RECORDS - iFound;

    pTab.SetIndex( IDX_ID);
    pTab.MoveFirst;
    while not pTab.Eof do begin

      // test whether or not this records is a full match
      if  ColumnContainsData( pTab, COL_BASE,     sBase,     nDummy)
      and ColumnContainsData( pTab, COL_SAUCE,    sSauce,    nDummy)
      and ColumnContainsData( pTab, COL_VEGETBLS, sVegtbl,   nDummy)
      and ColumnContainsData( pTab, COL_MEATFISH, sMeatFish, nDummy)
      and ColumnContainsData( pTab, COL_CHEESE,   sCheese,   nDummy)
      then Dec( iFound)   // full match
      else Dec( iRest);   // partial match, or no match at all

      pTab.MoveNext;
    end;

    // now both counters shosuld be zero again
    Expecting( iFound = 0, 'Matching records counter is '+IntToStr(iFound)+', expected was 0');
    Expecting( iRest  = 0, 'Non-matching records counter is '+IntToStr(iFound)+', expected was 0');
  end;
end;


procedure TCrossproductTestCase.RunAllTests;
var pTab : IJetTable;
begin
  pTab := FTuples.Table;

  FillTable( pTab);

  PerformTest( pTab);
end;



end.


